use super::*;

mod argument {
    use std::mem;

    use super::*;

    #[test]
    fn roundtrip_single_value() {
        let orig_val = 42u32;
        let orig_arg =
            Argument::with_value(&orig_val, ArgumentFlag::ARG_IN | ArgumentFlag::ARG_OUT);
        println!("orig_arg: {:?}", orig_arg);
        println!("orig_val: {}", orig_val);

        let mut buf = vec![0u8; orig_arg.serialized_size()];

        let write_len = orig_arg.serialize_into(&mut buf).unwrap();
        assert_eq!(write_len, orig_arg.serialized_size());

        let (read_len, new_arg) = Argument::deserialize_from(&buf).unwrap();
        println!("new_arg: {:?}", new_arg);
        assert_eq!(read_len, write_len);

        assert_eq!(new_arg.size, orig_arg.size);
        assert_eq!(new_arg.align, orig_arg.align);
        assert_eq!(new_arg.count, orig_arg.count);
        assert_eq!(new_arg.flag, orig_arg.flag);

        let value = new_arg.get_value::<u32>().unwrap();
        println!("new_val: {}", value);
        assert_eq!(*value, orig_val);
    }

    #[test]
    fn roundtrip_zerosized_value() {
        let orig_val = ();
        let orig_arg =
            Argument::with_value(&orig_val, ArgumentFlag::ARG_IN | ArgumentFlag::ARG_OUT);
        println!("orig_arg: {:?}", orig_arg);
        println!("orig_val: {:?}", orig_val);

        let mut buf = vec![0u8; orig_arg.serialized_size()];

        let write_len = orig_arg.serialize_into(&mut buf).unwrap();
        assert_eq!(write_len, orig_arg.serialized_size());

        let (read_len, new_arg) = Argument::deserialize_from(&buf).unwrap();
        println!("new_arg: {:?}", new_arg);
        assert_eq!(read_len, write_len);

        assert_eq!(new_arg.size, orig_arg.size);
        assert_eq!(new_arg.align, orig_arg.align);
        assert_eq!(new_arg.count, orig_arg.count);
        assert_eq!(new_arg.flag, orig_arg.flag);

        let value = new_arg.get_value::<()>().unwrap();
        println!("new_val: {:?}", value);
        assert_eq!(*value, orig_val);
    }

    #[test]
    fn roundtrip_complex_value() {
        #[derive(Debug, PartialEq)]
        #[repr(C)]
        struct OtherStruct {
            value: u64,
        }

        #[derive(Debug, PartialEq)]
        #[repr(C)]
        struct ComplexStruct {
            val1: bool,
            val2: u32,
            val3: [u8; 7],
            val4: Box<[u8]>,
            val5: OtherStruct,
        }

        let orig_val = ComplexStruct {
            val1: true,
            val2: u32::MAX,
            val3: [0xFF; 7],
            val4: vec![1, 2, 3, 4].into_boxed_slice(), // WARNING: THIS NEEDS TO BE DEEP CLONED
            val5: OtherStruct { value: u64::MAX },
        };

        let orig_arg = Argument::with_value(&orig_val, ArgumentFlag::ARG_VIRT);
        println!("orig_arg: {:?}", orig_arg);
        println!("orig_val: {:?}", orig_val);

        let mut buf = vec![0u8; orig_arg.serialized_size()];
        let write_len = orig_arg.serialize_into(&mut buf).unwrap();

        let padding_start = mem::size_of::<Argument>();
        let padding_end = padding_start + orig_arg.serialized_padding();
        assert!(buf[padding_start..padding_end].iter().all(|&x| x == 0));

        let (read_len, new_arg) = Argument::deserialize_from(&buf).unwrap();
        println!("new_arg: {:?}", new_arg);
        assert_eq!(read_len, write_len);

        let new_val = new_arg.get_value::<ComplexStruct>().unwrap();
        println!("new_val: {:?}", new_val);
        assert_eq!(new_val, &orig_val);
    }

    #[test]
    fn roundtrip_slice() {
        let orig_val = [1, 2, 3, 4];
        let orig_arg = Argument::with_slice(&orig_val, ArgumentFlag::ARG_OUT);
        println!("orig_arg: {:?}", orig_arg);
        println!("orig_val: {:?}", orig_val);

        let mut buf = vec![0u8; orig_arg.serialized_size()];

        let write_len = orig_arg.serialize_into(&mut buf).unwrap();
        assert_eq!(write_len, orig_arg.serialized_size());

        let (read_len, new_arg) = Argument::deserialize_from(&buf).unwrap();
        println!("new_arg: {:?}", new_arg);
        assert_eq!(read_len, write_len);

        assert_eq!(new_arg.size, orig_arg.size);
        assert_eq!(new_arg.align, orig_arg.align);
        assert_eq!(new_arg.count, orig_arg.count);
        assert_eq!(new_arg.flag, orig_arg.flag);

        let new_val = new_arg.get_value::<[u64; 4]>().unwrap().as_slice();
        println!("new_val: {:?}", new_val);
        assert_eq!(new_val, &orig_val);
    }

    #[test]
    fn roundtrip_empty_slice() {
        let orig_val = [];
        let orig_arg = Argument::with_slice(&orig_val, ArgumentFlag::ARG_OUT);
        println!("orig_arg: {:?}", orig_arg);
        println!("orig_val: {:?}", orig_val);

        let mut buf = vec![0u8; orig_arg.serialized_size()];

        let write_len = orig_arg.serialize_into(&mut buf).unwrap();
        assert_eq!(write_len, orig_arg.serialized_size());

        let (read_len, new_arg) = Argument::deserialize_from(&buf).unwrap();
        println!("new_arg: {:?}", new_arg);
        assert_eq!(read_len, write_len);

        assert_eq!(new_arg.size, orig_arg.size);
        assert_eq!(new_arg.align, orig_arg.align);
        assert_eq!(new_arg.count, orig_arg.count);
        assert_eq!(new_arg.flag, orig_arg.flag);

        let new_val = new_arg.get_value::<[(); 0]>().unwrap().as_slice();
        println!("new_val: {:?}", new_val);
        assert_eq!(new_val, &orig_val);
    }

    #[test]
    fn roundtrip_zerosized_slice() {
        let orig_val = [(), (), (), ()];
        let orig_arg = Argument::with_slice(&orig_val, ArgumentFlag::ARG_OUT);
        println!("orig_arg: {:?}", orig_arg);
        println!("orig_val: {:?}", orig_val);

        let mut buf = vec![0u8; orig_arg.serialized_size()];

        let write_len = orig_arg.serialize_into(&mut buf).unwrap();
        assert_eq!(write_len, orig_arg.serialized_size());

        let (read_len, new_arg) = Argument::deserialize_from(&buf).unwrap();
        println!("new_arg: {:?}", new_arg);
        assert_eq!(read_len, write_len);

        assert_eq!(new_arg.size, orig_arg.size);
        assert_eq!(new_arg.align, orig_arg.align);
        assert_eq!(new_arg.count, orig_arg.count);
        assert_eq!(new_arg.flag, orig_arg.flag);

        let new_val = new_arg.get_value::<[(); 4]>().unwrap().as_slice();
        println!("new_val: {:?}", new_val);
        assert_eq!(new_val, &orig_val);
    }

    #[test]
    fn alignment_padding() {
        #[derive(Debug, PartialEq, Eq)]
        struct TestType(u128); // 16 bytes aligned

        let orig_val = TestType(42);
        let orig_arg = Argument::with_value(&orig_val, ArgumentFlag::ARG_VIRT);
        println!("orig_arg: {:?}", orig_arg);
        println!("orig_val: {}", orig_val.0);

        let mut buf = vec![0u8; orig_arg.serialized_size()];

        let write_len = orig_arg.serialize_into(&mut buf).unwrap();
        assert_eq!(write_len, orig_arg.serialized_size());

        let padding_start = mem::size_of::<Argument>();
        let padding_end = padding_start + orig_arg.serialized_padding();
        assert!(buf[padding_start..padding_end].iter().all(|&x| x == 0));

        let (read_len, new_arg) = Argument::deserialize_from(&buf).unwrap();
        println!("new_arg: {:?}", new_arg);
        assert_eq!(read_len, write_len);

        let new_val = new_arg.get_value::<TestType>().unwrap();
        println!("new_val: {}", new_val.0);
        assert_eq!(new_val, &orig_val);
        assert_eq!(new_arg.align, orig_arg.align);
    }

    #[test]
    fn error_handling() {
        let original = Argument::with_value(&10u16, ArgumentFlag::ARG_IN);

        let mut buff1 = [0u8; 1];
        let result1 = original.serialize_into(&mut buff1);
        println!("{:?}", result1);
        assert!(matches!(
            result1,
            Err(SerializeError::InsufficientBuffer { .. })
        ));

        let buff2 = vec![0u8; 256];
        let result2 = Argument::deserialize_from(&buff2[1..]);
        println!("{:?}", result2);
        assert!(matches!(
            result2,
            Err(SerializeError::UnalignedAccess { .. })
        ));
    }
}
